use super::common;
use anyhow::{anyhow, Result};
use std::{
    fs::{self, File, OpenOptions},
    io::{Read, Write},
    path::PathBuf,
};
use structopt::StructOpt;
use tracing::{error, info};
use uuid::Uuid;

#[derive(StructOpt)]
pub enum Bom {
    /// 添加文件的 BOM 头
    Add {
        #[structopt(parse(from_os_str))]
        path: PathBuf,
    },
    /// 添加文件的 BOM 头
    Remove {
        #[structopt(parse(from_os_str))]
        path: PathBuf,
    },
    Exist {
        #[structopt(parse(from_os_str))]
        path: PathBuf,
    },
}

pub fn handle_bom(bom: Bom) {
    match bom {
        Bom::Add { path } => match add_bom(&path) {
            Ok(_) => info!("add bom success"),
            Err(e) => error!("add bom failed: {}", e),
        },
        Bom::Remove { path } => match remove_bom(&path) {
            Ok(_) => info!("remove bom success"),
            Err(e) => error!("remove bom failed: {}", e),
        },
        Bom::Exist { path } => match exist_bom(&path) {
            Ok(b) => info!("bom exist: {}", b),
            Err(e) => error!("bom exist failed: {}", e),
        },
    }
}

fn add_bom(path: &PathBuf) -> Result<()> {
    let exist = exist_bom(&path)?;
    if exist {
        return Err(anyhow!("add_bom: file has bom"));
    }

    let mut bom = vec![0xEF, 0xBB, 0xBF];
    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .open(&path)
        .map_err(|err| anyhow!("add_bom:open_file {}", err))?;
    file.write_all(&mut bom)
        .map_err(|err| anyhow!("add_bom:write_bom: {}", err))?;
    Ok(())
}

fn remove_bom(path: &PathBuf) -> Result<()> {
    let exist = exist_bom(&path)?;
    if !exist {
        return Err(anyhow!("remove_bom: file has no bom"));
    }
    let mut file = OpenOptions::new().read(true).write(true).open(&path)?;
    file.read(&mut [0; 3])?;
    let mut buf = vec![0; 102400];
    let temp_file_path = PathBuf::from(format!("{}.ytool", Uuid::new_v4().to_string()));
    println!("tmep file path: {}", temp_file_path.display());
    let mut temp_file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(&temp_file_path)?;
    while file.read(&mut buf)? != 0 {
        temp_file.write(&buf)?;
    }
    fs::remove_file(path)?;
    fs::rename(&temp_file_path, &path)?;
    Ok(())
}

fn exist_bom(path: &PathBuf) -> Result<bool> {
    common::file_exist(path)?;

    let mut file = File::open(path)?;
    let mut buf = vec![0; 3];
    if file.read(&mut buf)? < 3 {
        return Err(anyhow!("exist_bom: empty file"));
    }
    if buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF {
        return Ok(true);
    }
    return Ok(false);
}
